#!/usr/bin/env bash

set -Eeuo pipefail
# no shopt -s inherit_errexit as this needs to run on the ancient Bash in RHEL7
# instead, has 'set -e; ' below as recommended by shellcheck

# pass the packages to lint as args to the script

# shellcheck source-path=SCRIPTDIR
. cki_utils.sh

cki_say "preparing"
# shellcheck disable=SC2154,SC2310
if cki_is_true "${CKI_SKIP_PREPARE:-false}"; then
    cki_echo_yellow "skipping prepare because CKI_SKIP_PREPARE is true"
else
    PIP_INSTALL=(python3 -m pip install)
    python_type=$(type -P python3)
    if [[ ${python_type} = /usr* ]]; then
        PIP_INSTALL+=(--user)
    fi
    if [[ -f constraints.txt ]]; then
        PIP_INSTALL+=(--constraint constraints.txt)
    fi

    # in MR jobs, work around stale cki-lib from the gitlab-runner cache
    if [[ -v CI_MERGE_REQUEST_PROJECT_ID ]] && [[ -z "${cki_lib_pip_url:-}" ]] && \
            grep -F 'git+https://gitlab.com/cki-project/cki-lib.git' setup.cfg; then
        cki_lib_pip_url=git+https://gitlab.com/cki-project/cki-lib.git@production
    fi

    # if cki-lib requires overriding, update it and restart the script.
    if [[ -n "${cki_lib_pip_url:-}" ]] && ! [[ -v SKIP_FURTHER_CKI_LINT_EXEC ]]; then
        cki_echo_yellow "Using cki-lib override: ${cki_lib_pip_url}"
        python3 -m pip uninstall -y "cki-lib"
        echo "Running ${PIP_INSTALL[*]} ${cki_lib_pip_url}"
        "${PIP_INSTALL[@]}" "${cki_lib_pip_url}"
        cki_echo_yellow "Re-executing cki_lint.sh from updated cki-lib"
        SKIP_FURTHER_CKI_LINT_EXEC=true exec cki_lint.sh "$@"
    fi

    # Install linting requirements (append "@$cki_lib_pip_url" if defined)
    LINT_REQS="cki-lib[lint]${cki_lib_pip_url:+@${cki_lib_pip_url}}"
    cki_echo_yellow "Installing linting requirements with: ${PIP_INSTALL[*]} ${LINT_REQS}"
    "${PIP_INSTALL[@]}" "${LINT_REQS}"

    overrides_str=$(compgen -A variable | grep '_pip_url$' || true)
    readarray -t overrides <<< "${overrides_str}"
    for override in "${overrides[@]}"; do
        if [[ -z ${override} ]] || [[ ${!override} != git+https://* ]]; then
            continue
        fi
        pip_url=${!override}
        package_name=$(set -e; cki_git_clean_url "${pip_url%@*}")
        package_name=${package_name%.git/}
        package_name=${package_name##*/}

        # cki-lib was already handled
        if [[ ${package_name} = cki-lib ]]; then
            continue
        fi

        cki_echo_yellow "Found ${package_name} override: ${pip_url}"
        python3 -m pip uninstall -y "${package_name}"
        "${PIP_INSTALL[@]}" "${pip_url}"
    done
fi  # end of prepare

MAX_LINE_LENGTH=100
if [[ -f setup.cfg ]] && grep -q 'max-line-length=' setup.cfg; then
    MAX_LINE_LENGTH=$(sed -n s/max-line-length=//p setup.cfg | head -n 1)
    cki_echo_yellow "Overriding maximum line length with ${MAX_LINE_LENGTH}"
fi

# Download default ruff.toml if none is present
if [[ ! -f ruff.toml ]]; then
    global_ruff="https://gitlab.com/cki-project/cki-lib/-/raw/production/ruff.toml"
    # Don't fail the pipeline if this fails
    curl --fail --location "${global_ruff}" --output ruff.toml || true
fi

_isort=(
    isort
    --color
    --force-single-line-imports
    --force-sort-within-sections
    --line-length "${MAX_LINE_LENGTH}"
    --skip-glob '.*'
    --skip-glob '**/migrations'
)

cki_say "linting"

WARN=()
FAILED=()

# CKI_DISABLED_LINTERS: space separated list of linters to skip.
# The value `all` will skip all linters.
read -ra disabled_linters <<< "${CKI_DISABLED_LINTERS:-}"

function run_flake8() {
    cki_echo_yellow "Running flake8"
    if ! flake8 --max-line-length "${MAX_LINE_LENGTH}" --exclude ".*,migrations" .; then
        FAILED+=(flake8)
        cki_echo_red "  Run cki_lint.sh --fix to run autopep8"
    fi
}

function run_pydocstyle() {
    cki_echo_yellow "Running pydocstyle"
    if ! pydocstyle --match-dir='^(?!(\.|migrations)).*'; then
        FAILED+=(pydocstyle)
    fi
}

function run_isort() {
    cki_echo_yellow "Running isort"
    if ! "${_isort[@]}" "$@" .; then
        FAILED+=(isort)
        if [[ -z "$*" ]]; then
            cki_echo_red "  Run cki_lint.sh --fix to update import order"
        fi
    fi
}

function run_mypy() {
    # If mypy is installed (eg. via the dev extra), run mypy check
    if type mypy > /dev/null 2>&1; then
        cki_echo_yellow "Running mypy"
        if  ! mypy --strict; then
            WARN+=(mypy)
        fi
    fi
}

function run_markdownlint() {
    # Check and lint README.*.md files
    cki_echo_yellow "Running markdownlint and cki_check_links.sh"

    # Find all README files
    while read -r -d $'\0' file
    do
        # Lint Markdown
        if command -v markdownlint &> /dev/null; then
            # Config for the markdownlint
            markdownlint_config='{
            "line-length": {
                "line_length": 100,
                "code_blocks": false,
                "tables": false
            },
            "no-trailing-punctuation": {
                "punctuation": ".,;:"
            }
            }'
            markdown_config_file="${file%.*}_markdown_config.json"
            echo "${markdownlint_config}" > "${markdown_config_file}"
            markdown_errors_file="${file%.*}_markdown_errors.txt"
            markdownlint --config "${markdown_config_file}" "${file}" > "${markdown_errors_file}" || FAILED+=("markdownlint '${file}'")

            # Check if there are any errors in markdown or links files
            if [[ -s "${markdown_errors_file}" ]]; then
                cki_echo_red "Errors found in ${markdown_errors_file}:"
                cat "${markdown_errors_file}"
            else
                cki_echo_green "No errors found in ${markdown_errors_file}"
            fi

            # Remove *markdown_errors.txt files
            rm "${markdown_errors_file}"

            # Remove markdown_config_file
            rm "${markdown_config_file}"
        else
            cki_echo_yellow "Markdownlint not found. Skipping markdown linting."
        fi

        # Check links in Markdown
        if command -v urlchecker &> /dev/null; then
            cki_check_links.sh "${file}" || FAILED+=("cki_check_links.sh '${file}'")
        else
            cki_echo_yellow "urlchecker not found. Skipping checking links."
        fi

    done < <(find . -name "README*.md" -type f \
                    -not -path "./.tox/*" \
                    -not -path "./.direnv/*" \
                    -not -path "./.pytest_cache/*" \
                    -print0 || true)
}

function run_pylint() {
    # Only run pylint and coverage for the specified packages
    cki_echo_yellow "Running pylint"
    pylint_args=(
        -j 0
        --max-line-length "${MAX_LINE_LENGTH}"
        --load-plugins pylint.extensions.no_self_use
        --disable "broad-except,broad-exception-raised,possibly-used-before-assignment"
    )
    if [[ -f setup.cfg ]]; then
        pylint_args+=(--rcfile setup.cfg)
    fi
    if ! pylint "${pylint_args[@]}" "$@"; then
        FAILED+=(pylint)
    fi
}

function run_ruff() {
    # Run ruff in a non-blocking way
    cki_echo_yellow "Running ruff"
    if [[ -f ruff.toml ]]; then
        ruff_args=(--config ruff.toml)
    fi
    if ! ruff check "${ruff_args[@]}" "$@"; then
        WARN+=(ruff)
    fi
}

if [[ ${1:-} = "--fix" ]]; then
    shift

    # Try to fix issues in ALL Python files via autopep8 and isort

    cki_echo_yellow "Running autopep8"
    if ! autopep8 --max-line-length "${MAX_LINE_LENGTH}" --exclude "migrations" --in-place --recursive .; then
        FAILED+=(autopep8)
    fi

    maybe_run_linter isort "${disabled_linters[*]}"
else
    # Check ALL Python files with flake8 (which includes pycodestyle), pydocstyle and isort

    read -ra pylint_args <<< "${CKI_PYLINT_ARGS:-}"

    maybe_run_linter markdownlint "${disabled_linters[*]}"
    maybe_run_linter flake8 "${disabled_linters[*]}"
    maybe_run_linter pydocstyle "${disabled_linters[*]}"
    maybe_run_linter isort "${disabled_linters[*]}" --check --diff
    maybe_run_linter mypy "${disabled_linters[*]}"
    maybe_run_linter pylint "${disabled_linters[*]}" "${pylint_args[@]}" "$@"
    maybe_run_linter ruff "${disabled_linters[*]}"
fi

if (( ${#WARN[@]} > 0 )); then
    cki_echo_red "Failed linting steps (ignored): ${WARN[*]}"
    touch .cki_lint_warn
fi
if (( ${#FAILED[@]} > 0 )); then
    cki_echo_red "Failed linting steps: ${FAILED[*]}"
    exit 1
fi
